package com.security.config;

import com.security.handler.MyAuthenticationFailureHandler;
import com.security.handler.MyAuthenticationSuccessHandler;
import com.security.passwordencoder.MyMD5PasswordEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @USER: ysx
 * @CREATE: 2021/1/26 11:28
 */
@Configuration
public class MySimpleSecurityConfiguration extends WebSecurityConfigurerAdapter {

    /**
     * 重写父类型中的配置逻辑
     * 如果调用了super.configure,则使用父类型的默认流程配置
     * 需要自定义，则删除super.configure方法的调用
     * 当提供自定义配置，删除super.configure方法的时候，所有的默认流程全部清空
     *
     * http.formLogin().loginProcessingUrl(String path);
     *  用户登录请求地址。就是处理用户登录逻辑的地址
     *  SpringSecurity提供的处理登录请求控制器，是path监听软编码的。可以通过此方法动态配置监听地址
     *  只要配置地址和页面的请求地址一致，即可完成登录逻辑
     *  登录成功，默认返回的成功页面是'/'。如果有前置请求，则默认返回前置地址
     *      如：直接访问loginPage,登录成功进入'/'
     *      如：访问/ok,未登录，进入loginPage,登录成功后，重新返回/ok
     *  登录失败，默认返回的页面是http.formLogin().loginPage(String path) 方法参数?error
     *
     *  http.formLogin().defaultSuccessUrl("/toMain",true)  true必须传
     *
     *  登录请求参数命名：
     *      默认规则是： 用户名必须是username。密码必须是password。
     *      此逻辑由UsernamePasswordAuthenticationFilter决定
     *
     * @param //http  基于HTTP协议的Security配置对象。包含所有的SpringSecurity相关配置逻辑
     * @throws Exception    当配置出错的时候，抛出
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置登录请求相关内容
        http.formLogin()
                .usernameParameter("name") //设置请求参数中，用户名参数名称。默认username
                .passwordParameter("pswd") //设置请求参数中，密码参数名称，默认password
                .loginPage("/toLogin")  //用户未登录的时候，跳转的登录页面地址是什么？默认/login
                .loginProcessingUrl("/suibian")  //用户登录逻辑请求地址是什么 默认是/login
                .successHandler(new MyAuthenticationSuccessHandler("/toMain", true))
                .failureHandler(new MyAuthenticationFailureHandler("/to", true)); //自定义认证失败后的处理逻辑

//                .defaultSuccessUrl("/toMain",true); //用户登录成功后，响应重定向到的位置，GET请求,必须配置true参数
//                .successForwardUrl("/toMain"); //用户登录成功后，请求转发到的地址，Security请求转发使用POST请求

        //配置权限校验。如：什么地址必须认证后才能访问，什么地址可以不认证就访问
        //如：登录页面，不认证就可以访问，其他页面必须认证后才可以访问
        //权限校验的配置，是线性的。从开始的配置位置开始校验，成功立刻返回
        //校验匹配失败，继续后续的校验逻辑
        //如：请求/toLogin 校验antMatchers成功，授予所有权限，返回。后续的anyRequests不执行
        //如：请求/suibian 校验antMatchers失败，执行后续的anyRequests校验，要求必须认证后才能访问

        /**
         * 授予权限的方法逻辑
         * 授予权限的方法，都是基于路径地址匹配基础上调用的
         * 常用方法：
         *  permitAll() - 免登陆访问。任意用户任意状态都可以访问。描述通用资源如：网站首页 描述静态资源
         *  anonymous() - 匿名访问。登录后不可访问。如：登录页面和登录请求
         *  authenticated() - 登录后可以访问。描述保护资源的 如：个人信息，订单查询，密码修改等
         *  denyAll() - 任意用户任意状态都不可访问
         *  fullyAuthenticated() - 完整登录才可以访问。描述敏感资源 如：钱的操作
         *  rememberMe() - 记住我，自动登录|n天免登录。描述非敏感资源的。如：除钱相关操作以外的内容
         *  上述的所有方法，底层调用的都是统一的一个方法access()
         *      access - 可以实现任何权限授予逻辑。是基于权限表达式的权限授予方法
         *          表达式规则由Spring Security定义
         */
        http.authorizeRequests()
                .antMatchers("/toLogin", "/failure", "/favicon.ico").permitAll() //toLogin请求地址，可以随便访问
                .regexMatchers(".*[.]js").permitAll()
                .regexMatchers(".*[.]css").permitAll()

                //使用自定义代码逻辑，实现权限校验，返回true|false字符串
                //在access方法的参数中，可以使用SpringEL表达式传递代码运行逻辑
                //@beanName 可以获取Spring容器中的对象
                //@beanName.methodName()可以调用对象中的方法
                //方法参数要传递的对象，可以在WebSecurityExpressionRoot中查看，是这个类型中的所有属性。
                .antMatchers("/**").access("@myAuthorityPermitImpl.hasAuthority(request, authentication)")

//                .antMatchers("/ip").access("hasIpAddress('0:0:0:0:0:0:0:1')")
                //注意：ip规则访问要求完整匹配。localhost -> 0:0:0:0:0:0:0:1
                //127.0.0.1 -> 127.0.0.1  192.168.151.240 -> 192.168.151.240
                //客户端地址，可以在请求头中查看到。 解析是由request.getRemoteAddr处理的
                //限制客户端的时候使用。如：提供给内部指定客户端的服务    使用固定ip比较稳定
//                .antMatchers("/ip").hasIpAddress("127.0.0.1")//客户端ip符合规则的可以访问
//                .antMatchers("*.js").permitAll()
//                .antMatchers("*.css").permitAll()

//                //基于资源的权限管理
//                //hasAuthority("xxx") 等同于access("hasAuthority('xxx')")
//                .antMatchers("/admin/read").hasAnyAuthority("admin:read")//拥有admin:read权限的用户可以访问
//                .antMatchers("/admin/write").access("hasAuthority('admin:write')")
//                //access("hasAnyAuthority('guest:read', 'admin:read')")等同于hasAnyAuthority('guest:read', 'admin:read')
//                .antMatchers("/guest/read").access("hasAnyAuthority('guest:read', 'admin:read')")
//                //拥有guest:read或admin:read权限的用户可以访问
//                .antMatchers("/guest/write").hasAnyAuthority("guest:write")

//                //基于角色的权限管理
//                //hasRole("xxx")相当于调用access("hasRole('ROLE_xxx')")
//                .antMatchers("/admin/read").hasRole("管理员")
//                //请求地址为/admin/read的请求，必须登录用户拥有管理员权限才能访问
//                //hasRole("x","y","z") //相当于access("hasRole('ROLE_xxx')")
//                .antMatchers("/guest/read").hasAnyRole("管理员","访客")
//                //登录用户有访客或管理员角色，可以访问
//                .antMatchers("/guest/write").hasRole("访客")
//                //必须访客角色才可访问

                .anyRequest().authenticated(); //任意的请求，都必须认证后才能访问

        //关闭CSRF安全协议。
        //关闭是为了保证完整流程的可用
        http.csrf().disable();
    }



    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
